/**
 * 
 */
package com.gragra.sampling.vector;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntIterator;
import java.util.LinkedList;
import com.gragra.distances.DistanceTools;
import com.gragra.sampling.PopulationProbabilityAssigner;
import com.gragra.sampling.PopulationSamplingStructure;
import com.gragra.sampling.ThreadedUniformAccess;
import com.gragra.util.StringToInt;
/**
 * This implements Vectors as a datastructure mainly for the use with sampling based algorithms.
 * The vectors are represented in a sparse why in that for every value there is a corresponding (unit,element)
 * pair to it. The units are used to group values together and should have consistent semantics whenever more
 * than one vector is used. 
 * @author Christoph Teichmann
 * created Jan 11, 2013 8:20:15 PM
 * @version 0.1
 */
public class VectorStructure  implements PopulationSamplingStructure
{
	/**
	 * 
	 */
	private final static LinkedList<DoubleArrayList> pool = new LinkedList<DoubleArrayList>();
	/**
	 * 
	 */
	private static DoubleArrayList getArrayListFromPool()
	{
		synchronized(pool)
		{
			if(pool.isEmpty())
			{return new DoubleArrayList();}
			else
			{return pool.poll();}
		}
	}
	/**
	 * 
	 * @param da
	 */
	private static void returnToPool(DoubleArrayList da)
	{
		da.clear();
		synchronized(pool){pool.add(da);}
	}
	/**
	 * @param label the label to set
	 */
	public void setLabel(int label, int thread)
	{this.labels[thread] = label;}
	/**
	 * each int[] in the returned array is a pair of {unit,element}
	 * @return the codes
	 */
	public int[][] getCodes()
	{return codes;}
	/**
	 * the code that is used for the name of this structure it should be possible to turn this into
	 * a String with the reverse method of the StringToInt class
	 */
	private final int name;
	/**
	 * contains the values that form this vector
	 */
	private final double[] values;
	/**
	 * contains the codes that are used to identify the semantics of the values
	 */
	private final int[][] codes;
	/**
	 * contains the label that has currently been assigned by the sampling process
	 */
	private int[] labels;
	/**
	 * returns the current code for the name of the structure that can be mapped to a string by calling 
	 * StringToInt.reverse
	 * @return the name
	 */
	public int getName()
	{return name;}
	/**
	 * the best label seen in the sampling process so far
	 */
	private int bestLabel = -1;
	/**
	 * @return the length
	 */
	public int getLength()
	{return this.values.length;}
	/**
	 * this contains the number of times that a label has been seen during the sampling process
	 */
	private final Int2DoubleOpenHashMap labelCounts = new Int2DoubleOpenHashMap();
	/**
	 * 
	 * @param label
	 * @return
	 */
	public double getLabelCount(int label)
	{return this.labelCounts.get(label);}
	/**
	 * this is used to construct a new instance of the vector structures the name given must be translatable into
	 * a string by the StringToInt.reverse method
	 * @param name
	 * @param values
	 * @param codes
	 */
	public VectorStructure(final int name,final double[] values,final int[][] codes, int threads)
	{
		this.name = name;
		this.values = values;
		this.codes = codes;
		assert(this.codes.length == this.values.length);
		this.labels = new int[threads];
	}
	/**
	 * 
	 * @param probabilities
	 * @param labelNumber
	 * @param annealing
	 * @param tua
	 * @param i
	 * @return
	 */
	private int choose(DoubleArrayList probabilities, int labelNumber,
			double annealing, ThreadedUniformAccess tua, int thread)
	{
		double max = Double.NEGATIVE_INFINITY;
		for(int i=0;i<probabilities.size();++i)
		{max = Math.max(max, probabilities.getDouble(i));}
		double sum = 0.0;
		for(int i=0;i<probabilities.size();++i)
		{
			double add = Math.exp(probabilities.get(i)-max);
			sum += add;
			probabilities.set(i, add);
		}
		double border = tua.nextDouble(thread);
		for(int i=0;i<probabilities.size();++i)
		{
			border -= probabilities.getDouble(i)/sum;
			if(border <= 0.0)
			{return i;}
		}
		throw new IllegalStateException("probabilities did not sum to one");
	}
	/* (non-Javadoc)
	 * @see com.gragra.sampling.SamplingStructure#addSample()
	 */
	@Override
	public void addSample()
	{
		for(int i=0;i<labels.length;++i)
		{this.labelCounts.addTo(labels[i], 1.0);}
	}
	/**
	 * @return
	 */
	protected String giveName()
	{
		String s = StringToInt.reverse(name);
		return s == null ? "vector" : s;
	}
	/**
	 * used to select the most frequently seen label assignment after the sampling process
	 * @param counts
	 * @return
	 */
	private int pickMax(Int2DoubleOpenHashMap counts)
	{
		double best = Double.NEGATIVE_INFINITY;
		int max = -1;
		IntIterator iit = counts.keySet().iterator();
		for(;iit.hasNext();)
		{
			int i = iit.nextInt();
			double d = counts.get(i);
			if(d > best)
			{
				best = d;
				max = i;
			}
		}
		if(max < 0)
		{throw new IllegalStateException("no entry was better than negative infinity");}
		return max;
	}
	/**
	 * returns the values that are contained in this vector, this returns the vector that is internally used,
	 * so changing it will change the behavior of the instance
	 * @return
	 */
	public double[] getValues()
	{return this.values;}
	/**
	 * returns the label that has currently been assigned to the vector during the sampling process
	 * @return
	 */
	public int getLabel(int thread)
	{return this.labels[thread];}
	/**
	 * returns the best label seen so far
	 * @return
	 */
	public int getBestIntCode()
	{return this.bestLabel;}
	/**
	 * returns the l2 norm of the vector, is only computed on the first call
	 * @return
	 */
	public double getL2Norm()
	{
		if(norm < 0.0)
		{makeNorm();}
		return norm;
	}
	/**
	 * used to compute the L2 norm of the vector
	 */
	private void makeNorm()
	{this.norm = DistanceTools.getL2Norm(this.values);}
	/**
	 * hold the calculated value for the l2 norm
	 */
	private double norm = -1.0;
	@Override
	public void resample(PopulationProbabilityAssigner pa, double annealing,
			int thread, ThreadedUniformAccess tua)
	{
		VectorProbabilityAssigner va = (VectorProbabilityAssigner) pa;
		int labelNumber = va.getLabelNumber(this,thread);
		DoubleArrayList probabilities = getArrayListFromPool();
		probabilities.clear();
		for(int i=0;i<labelNumber;++i)
		{
			this.labels[thread] = i;
			probabilities.add(va.makeLogProb(this, thread));
		}
		this.labels[thread] = this.choose(probabilities, labelNumber,annealing,tua,thread);
		returnToPool(probabilities);
	}
	@Override
	public String getBest(PopulationProbabilityAssigner pa)
	{return giveName()+" : "+Integer.toString(bestLabel);}
	@Override
	public String minRisk(PopulationProbabilityAssigner pa)
	{
		int i = this.pickMax(this.labelCounts);
		return giveName()+" : "+Integer.toString(i);
	}
	@Override
	public void setBest(int thread)
	{this.bestLabel = this.labels[thread];}
	@Override
	public void initialize(PopulationProbabilityAssigner pa,
			ThreadedUniformAccess tua)
	{
		for(int thread=0;thread<this.labels.length;++thread)
		{this.resample(pa, 1.0, thread, tua);}
	}
}